#include "MyMatrix.h"

#pragma region Constructor
// Empty Constructor
template <class T>
MyMatrix<T>::MyMatrix() : _col(0), _row(0), _mat(nullptr)
{
    _pcnt = new uint{0};
}

// Zero Square Matrix Constructor
template <class T>
MyMatrix<T>::MyMatrix(const uint squareRowCol) : _row(squareRowCol), _col(squareRowCol)
{
    _pcnt = new uint{0};
    _mat = new T[_col * _row]{0};
}

// Zero Matrix Constructor
template <class T>
MyMatrix<T>::MyMatrix(const uint row, const uint col) : _row(row), _col(col)
{
    _pcnt = new uint{0};
    _mat = new T[_col * _row]{0};
}

// Buffer-Matrix Constructor
template <class T>
template <uint Size>
MyMatrix<T>::MyMatrix(const T (&buffer)[Size], const uint row, const uint col) : _row(row), _col(col)
{
    _pcnt = new uint{0};
    _mat = new T[_col * _row]{0};
    uint total = _row * _col;
    if (total < Size)
    {
        memcpy(_mat, buffer, total * sizeof(T));
    }
    else
    { // total >= Size
        memcpy(_mat, buffer, Size * sizeof(T));
    }
}

// Buffer-Matrix Constructor - Unsafe
template <class T>
MyMatrix<T>::MyMatrix(const T buffer[], const uint row, const uint col)
{
    if (buffer == nullptr)
    {
        _row = 0;
        _col = col;
        _pcnt = new uint{0};
        _mat = nullptr;
    }
    else
    {
        _row = row;
        _col = col;
        _pcnt = new uint{0};
        _mat = new T[_col * _row]{0};
        uint total = _row * _col;
        // Assuming that buffer always has more data than total
        memcpy(_mat, buffer, total * sizeof(T));
    }
}

// Copy Constructor
template <class T>
MyMatrix<T>::MyMatrix(const MyMatrix<T> &m)
{
    this->_row = m._row;
    this->_col = m._col;

    if (*m._pcnt < UINT_MAX)
    {
        ++(*m._pcnt);

        this->_pcnt = m._pcnt;
        this->_mat = m._mat;
    }
    else
    {
        this->_pcnt = new uint{0};
        this->_mat = new T[this->_col * this->_row]{0};
        memcpy(this->_mat, m._mat, m._row * m._col * sizeof(T));
    }
}
#pragma endregion

#pragma region Destructor
template <class T>
MyMatrix<T>::~MyMatrix()
{
    if (*this->_pcnt == 0)
    {
        delete this->_pcnt;
        delete[] this->_mat;
    }
    else
    {
        --(*this->_pcnt);
    }
}
#pragma endregion

#pragma region CheckTools
template <class T>
bool MyMatrix<T>::isEmpty() const
{
    return _mat == nullptr || _row == 0 || _col == 0;
}

template <class T>
bool MyMatrix<T>::checkSameShape(const MyMatrix<T> &m) const
{
    return this->_row == m._row && this->_col == m._col;
}

template <class T>
bool MyMatrix<T>::checkMultiply(const MyMatrix<T> &m) const
{
    return this->_col == m._row;
}

template <class T>
bool MyMatrix<T>::is(MyMatrix<T> m) const
{
    return this->_mat == m._mat;
}

template <class T>
bool MyMatrix<T>::equals(MyMatrix<T> m) const
{
    if (this->is(m))
        return true;
    if (_row != m._row || _col != m._col)
        return false;
    bool flag = true;
    for (uint r = 0; r < _row; ++r)
    {
        for (uint c = 0; c < _col; ++c)
        {
            flag = flag && (this->_mat[r][c] == m._mat[r][c]);
        }
    }
    return flag;
}
#pragma endregion

#pragma region Math
template <class T>
MyMatrix<T> MyMatrix<T>::fill(const T &t)
{
    if (isEmpty())
        return MyMatrix();

    for (uint r = 0; r < this->_row; ++r)
    {
        for (uint c = 0; c < this->_col; ++c)
        {
            (*this)[r][c] = t;
        }
    }
    return *this;
}

#pragma region Add
template <class T>
MyMatrix<T> MyMatrix<T>::operator+(const MyMatrix<T> &m) const
{
    MyMatrix<T> res = *this;
    res += m;
    return res;
}

template <class T>
MyMatrix<T> MyMatrix<T>::operator+(const T &t) const
{
    MyMatrix<T> res(this->_mat, this->_row, this->_col);

    for (uint r = 0; r < res._row; ++r)
    {
        for (uint c = 0; c < res._col; ++c)
        {
            res[r][c] = res[r][c] + t;
        }
    }

    return res;
}

template <class T>
MyMatrix<T>& MyMatrix<T>::operator+=(const MyMatrix<T> &m)
{
    if (!checkSameShape(m))
        throw -1;

    // Seperate with the origin mat
    if (*this->_pcnt != 0)
    {
        --(*this->_pcnt);
        this->_pcnt = new uint(0);
        T *temp = new T[this->_row * this->_col];
        memcpy(temp, this->_mat, this->_row * this->_col * sizeof(T));
        this->_mat = temp;
    }

    // Do the add
    for (uint r = 0; r < this->_row; ++r)
    {
        for (uint c = 0; c < this->_col; ++c)
        {
            (*this)[r][c] += m[r][c];
        }
    }
    return *this;
}
#pragma endregion

#pragma region Subtraction
template <class T>
MyMatrix<T> MyMatrix<T>::operator-(const MyMatrix<T> &m) const
{
    MyMatrix<T> res = *this;
    res -= m;
    return res;
}

template <class T>
MyMatrix<T> MyMatrix<T>::operator-(const T &t) const
{
    MyMatrix<T> res(this->_mat, this->_row, this->_col);

    for (uint r = 0; r < res._row; ++r)
    {
        for (uint c = 0; c < res._col; ++c)
        {
            res[r][c] = res[r][c] - t;
        }
    }

    return res;
}

template <class T>
MyMatrix<T>& MyMatrix<T>::operator-=(const MyMatrix<T> &m)
{
    if (!checkSameShape(m))
        throw -1;

    // Seperate with the origin mat
    if (*this->_pcnt != 0)
    {
        --(*this->_pcnt);
        this->_pcnt = new uint(0);
        T *temp = new T[this->_row * this->_col];
        memcpy(temp, this->_mat, this->_row * this->_col * sizeof(T));
        this->_mat = temp;
    }

    // Do the minus
    for (uint r = 0; r < this->_row; ++r)
    {
        for (uint c = 0; c < this->_col; ++c)
        {
            (*this)[r][c] -= m[r][c];
        }
    }
    return *this;
}
#pragma endregion

#pragma region Multiply
template <class T>
MyMatrix<T> MyMatrix<T>::operator*(const MyMatrix<T> &m) const
{
    if (!checkMultiply(m))
        throw -1;

    MyMatrix<T> res(this->_row, m._col);
    for (uint r = 0; r < this->_row; ++r)
    {
        for (uint c = 0; c < m._col; ++c)
        {
            for (uint i = 0; i < m._row; ++i)
            {
                res[r][c] += (*this)[r][i] * m[i][c];
            }
        }
    }
    return res;
}

template <class T>
MyMatrix<T> MyMatrix<T>::operator*(const T &t) const
{
    MyMatrix<T> res(this->_mat, this->_row, this->_col);

    for (uint r = 0; r < res._row; ++r)
    {
        for (uint c = 0; c < res._col; ++c)
        {
            res[r][c] = res[r][c] * t;
        }
    }

    return res;
}

template <class T>
MyMatrix<T>&MyMatrix<T>::operator*=(const MyMatrix<T> &m)
{
    *this = (*this) * m;
    return *this;
}
#pragma endregion

#pragma region Divide
template <class T>
MyMatrix<T> MyMatrix<T>::operator/(const T &t) const
{
    if (t == (T)0)
        throw -1;

    MyMatrix<T> res(this->_mat, this->_row, this->_col);

    for (uint r = 0; r < res._row; ++r)
    {
        for (uint c = 0; c < res._col; ++c)
        {
            res[r][c] = res[r][c] / t;
        }
    }

    return res;
}

#pragma endregion

#pragma region Assign
template <class T>
MyMatrix<T>& MyMatrix<T>::operator=(const MyMatrix<T> &m)
{
    // Description to be written

    // if this->_mat is the same with m._mat
    /* Example:
    *		MyMatrix a(buffer,row,col);
    *		MyMatrix b(a);
    *		b = a; or a = a;
    */
    if (this->_mat == m._mat)
        return *this;

    // Seperate with the origin mat
    // this->~MyMatrix();
    if (*this->_pcnt == 0)
    {
        delete this->_pcnt;
        delete[] this->_mat;
    }
    else
    {
        --(*this->_pcnt);
    }

    // Do the Copy
    this->_row = m._row;
    this->_col = m._col;
    if (*m._pcnt < UINT_MAX)
    {
        ++(*m._pcnt);
        this->_pcnt = m._pcnt;
        this->_mat = m._mat;
    }
    else
    {
        this->_pcnt = new uint{0};
        this->_mat = new T[this->_col * this->_row]{0};
        memcpy(this->_mat, m._mat, m._row * m._col * sizeof(T));
    }
    return *this;
}
#pragma endregion

#pragma region Minus &Positive
// ����
// the former 'const' prevents "-m = MyMatrix(...)" from happening
// the latter 'const' makes -(-m) can be calculated properly
template <class T>
const MyMatrix<T> MyMatrix<T>::operator-() const
{
    MyMatrix<T> res(this->_mat, this->_row, this->_col);

    for (uint r = 0; r < res._row; ++r)
    {
        for (uint c = 0; c < res._col; ++c)
        {
            res[r][c] = -res[r][c];
        }
    }
    return res;
}

// ����
// the former 'const' prevents "+m = MyMatrix(...)" from happening
// the latter 'const' makes +(+m) can be calculated properly
template <class T>
const MyMatrix<T> MyMatrix<T>::operator+() const
{
    return *this;
}
#pragma endregion

template <class T>
MyMatrix<T> MyMatrix<T>::transpose()
{
    MyMatrix<T> res(_col, _row);
    for (uint i = 0; i < _row; ++i)
    {
        for (uint j = 0; j < _col; ++j)
        {
            res[j][i] = _mat[i][j];
        }
    }
    return res;
}

#pragma region Basic
template <class T>
MyMatrix<T> MyMatrix<T>::zeros()
{
    return fill(0);
}

template <class T>
MyMatrix<T> MyMatrix<T>::zeros(const uint squareRowCol)
{
    return MyMatrix(squareRowCol);
}

template <class T>
MyMatrix<T> MyMatrix<T>::zeros(const uint row, const uint col)
{
    return MyMatrix(row, col);
}

template <class T>
MyMatrix<T> MyMatrix<T>::ones()
{
    return fill(1);
}

template <class T>
MyMatrix<T> MyMatrix<T>::ones(const uint squareRowCol)
{
    return MyMatrix(squareRowCol) + 1;
}

template <class T>
MyMatrix<T> MyMatrix<T>::ones(const uint row, const uint col)
{
    return MyMatrix(row, col) + 1;
}
#pragma endregion

#pragma region Subscript
template <class T>
T * MyMatrix<T>::operator[](const uint row)
{
    return this->_mat + row * this->_col;
}

template <class T>
const T * MyMatrix<T>::operator[](const uint row) const
{
    return this->_mat + row * this->_col;
}

template <class T>
T & MyMatrix<T>::operator()(const int row, const int col)
{
    if (row > _row - 1 || row < -_row)
        throw -1;
    if (col > _col - 1 || col < -_col)
        throw -1;

    if (*_pcnt != 0)
    {
        --(*this->pcnt);
        *this = MyMatrix(_mat, _row, _col);
    }

    if (row < 0)
    {
        if (col < 0)
        {
            return _mat[row + _row][col + _col];
        }
        else
        {
            return _mat[row + _row][col];
        }
    }
    else
    {
        if (col < 0)
        {
            return _mat[row][col + _col];
        }
        else
        {
            return _mat[row][col];
        }
    }
}

template <class T>
const T& MyMatrix<T>::operator()(const int row, const int col) const
{
    if (row > _row - 1 || row < -_row)
        throw -1;
    if (col > _col - 1 || col < -_col)
        throw -1;

    if (row < 0)
    {
        if (col < 0)
        {
            return _mat[row + _row][col + _col];
        }
        else
        {
            return _mat[row + _row][col];
        }
    }
    else
    {
        if (col < 0)
        {
            return _mat[row][col + _col];
        }
        else
        {
            return _mat[row][col];
        }
    }
}
#pragma endregion
#pragma endregion
